//! Test Suite Actor
//! 
//! This actor manages test suite execution, corresponding to HttpRunner's HRPRunner.
//! It coordinates the execution of multiple test cases and manages global configuration.

use crate::actors::messages::*;
use crate::actors::{SessionActor, PluginActor, AIOrchestrationActor, ReportActor};
use crate::error::{Error, Result, TestExecutionError};
use crate::models::{
    testcase::{TestCase, TestCasePath, ITestCase},
    config::TestConfig,
    summary::TestSummary,
    Value, ValueMap,
};
use coerce::actor::{Actor, ActorRef};
use coerce::actor::context::ActorContext;
use coerce::actor::message::Handler;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use tokio::fs;
use uuid::Uuid;

/// Test Suite Actor (corresponds to HttpRunner's HRPRunner)
#[derive(Debug)]
pub struct TestSuiteActor {
    /// Loaded test suites
    test_suites: HashMap<Uuid, TestSuiteData>,
    
    /// Active executions
    active_executions: HashMap<Uuid, ExecutionData>,
    
    /// Plugin actor reference
    plugin_actor: Option<ActorRef<PluginActor>>,

    /// AI orchestration actor reference
    ai_actor: Option<ActorRef<AIOrchestrationActor>>,

    /// Report actor reference
    report_actor: Option<ActorRef<ReportActor>>,
}

/// Test suite data
#[derive(Debug, Clone)]
struct TestSuiteData {
    id: Uuid,
    name: String,
    path: String,
    test_cases: Vec<TestCase>,
    config: TestConfig,
}

/// Execution data
#[derive(Debug, Clone)]
struct ExecutionData {
    id: Uuid,
    suite_id: Uuid,
    status: ExecutionStatus,
    progress: ExecutionProgress,
    session_actors: Vec<ActorRef<SessionActor>>,
    start_time: chrono::DateTime<chrono::Utc>,
    summary: Option<TestSummary>,
}

impl TestSuiteActor {
    /// Create a new test suite actor
    pub fn new(
        plugin_actor: ActorRef<PluginActor>,
        ai_actor: ActorRef<AIOrchestrationActor>,
        report_actor: ActorRef<ReportActor>,
    ) -> Self {
        Self {
            test_suites: HashMap::new(),
            active_executions: HashMap::new(),
            plugin_actor: Some(plugin_actor),
            ai_actor: Some(ai_actor),
            report_actor: Some(report_actor),
        }
    }
    
    /// Load test suite from path
    async fn load_test_suite(&mut self, path: String) -> Result<TestSuiteResponse> {
        tracing::info!("Loading test suite from: {}", path);
        
        let path_obj = Path::new(&path);
        
        if !path_obj.exists() {
            return Ok(TestSuiteResponse::Error {
                message: format!("Test suite path does not exist: {}", path),
            });
        }
        
        let test_cases = if path_obj.is_file() {
            // Single test case file
            vec![self.load_test_case_from_file(&path).await?]
        } else if path_obj.is_dir() {
            // Directory containing multiple test cases
            self.load_test_cases_from_directory(&path).await?
        } else {
            return Ok(TestSuiteResponse::Error {
                message: format!("Invalid test suite path: {}", path),
            });
        };
        
        if test_cases.is_empty() {
            return Ok(TestSuiteResponse::Error {
                message: "No test cases found in the specified path".to_string(),
            });
        }
        
        // Create test suite
        let suite_id = Uuid::new_v4();
        let suite_name = path_obj
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("unknown")
            .to_string();
        
        // Use the first test case's config as the suite config
        let config = test_cases[0].config.clone();
        
        let suite_data = TestSuiteData {
            id: suite_id,
            name: suite_name,
            path: path.clone(),
            test_cases: test_cases.clone(),
            config,
        };
        
        self.test_suites.insert(suite_id, suite_data);
        
        tracing::info!("Test suite loaded successfully: {} test cases", test_cases.len());
        
        Ok(TestSuiteResponse::SuiteLoaded {
            suite_id,
            test_cases,
        })
    }
    
    /// Load a single test case from file
    async fn load_test_case_from_file(&self, path: &str) -> Result<TestCase> {
        let test_case_path = TestCasePath::new(path);
        test_case_path.get_test_case().await
    }
    
    /// Load test cases from directory
    async fn load_test_cases_from_directory(&self, dir_path: &str) -> Result<Vec<TestCase>> {
        let mut test_cases = Vec::new();
        let mut entries = fs::read_dir(dir_path).await
            .map_err(|e| Error::TestExecution(TestExecutionError::TestCaseNotFound {
                path: dir_path.to_string(),
            }))?;
        
        while let Some(entry) = entries.next_entry().await.map_err(|e| {
            Error::TestExecution(TestExecutionError::InvalidTestCase {
                reason: format!("Failed to read directory entry: {}", e),
            })
        })? {
            let path = entry.path();
            
            if path.is_file() {
                if let Some(extension) = path.extension() {
                    if extension == "json" || extension == "yaml" || extension == "yml" {
                        match self.load_test_case_from_file(&path.to_string_lossy()).await {
                            Ok(test_case) => test_cases.push(test_case),
                            Err(e) => {
                                tracing::warn!("Failed to load test case from {}: {}", path.display(), e);
                            }
                        }
                    }
                }
            }
        }
        
        Ok(test_cases)
    }
    
    /// Execute test suite
    async fn execute_suite(
        &mut self,
        ctx: &mut ActorContext,
        suite_id: Uuid,
        config: TestConfig,
    ) -> Result<TestSuiteResponse> {
        let suite_data = self.test_suites.get(&suite_id).cloned()
            .ok_or_else(|| Error::TestExecution(TestExecutionError::TestCaseNotFound {
                path: format!("Suite ID: {}", suite_id),
            }))?;
        
        let execution_id = Uuid::new_v4();
        
        tracing::info!("Starting test suite execution: {} ({})", suite_data.name, execution_id);
        
        // Create execution data
        let execution_data = ExecutionData {
            id: execution_id,
            suite_id,
            status: ExecutionStatus::Running,
            progress: ExecutionProgress::new(suite_data.test_cases.len()),
            session_actors: Vec::new(),
            start_time: chrono::Utc::now(),
            summary: None,
        };
        
        self.active_executions.insert(execution_id, execution_data);
        
        // Start execution in background
        let test_cases = suite_data.test_cases.clone();
        let execution_config = config.clone();
        let plugin_actor = self.plugin_actor.clone();
        let ai_actor = self.ai_actor.clone();
        let report_actor = self.report_actor.clone();
        
        // Spawn execution task
        tokio::spawn(async move {
            // TODO: Implement actual test case execution
            // This will create session actors for each test case and coordinate their execution
            tracing::info!("Test suite execution started with {} test cases", test_cases.len());
        });
        
        Ok(TestSuiteResponse::ExecutionStarted { execution_id })
    }
    
    /// Get suite status
    async fn get_suite_status(&self, suite_id: Uuid) -> Result<TestSuiteResponse> {
        // Find active execution for this suite
        let execution = self.active_executions
            .values()
            .find(|exec| exec.suite_id == suite_id)
            .cloned();
        
        if let Some(exec) = execution {
            Ok(TestSuiteResponse::StatusInfo {
                suite_id,
                status: exec.status,
                progress: exec.progress,
            })
        } else {
            Ok(TestSuiteResponse::StatusInfo {
                suite_id,
                status: ExecutionStatus::Pending,
                progress: ExecutionProgress::new(0),
            })
        }
    }
    
    /// Stop execution
    async fn stop_execution(&mut self, suite_id: Uuid) -> Result<TestSuiteResponse> {
        // Find and stop active execution
        let execution_ids: Vec<Uuid> = self.active_executions
            .iter()
            .filter(|(_, exec)| exec.suite_id == suite_id)
            .map(|(id, _)| *id)
            .collect();
        
        for execution_id in execution_ids {
            if let Some(mut execution) = self.active_executions.remove(&execution_id) {
                execution.status = ExecutionStatus::Cancelled;
                
                // Note: Session actors will be automatically cleaned up when the system shuts down
                // We don't need to manually stop them in coerce-rs
                
                tracing::info!("Stopped execution: {}", execution_id);
            }
        }
        
        Ok(TestSuiteResponse::StatusInfo {
            suite_id,
            status: ExecutionStatus::Cancelled,
            progress: ExecutionProgress::new(0),
        })
    }
    
    /// List all test suites
    async fn list_suites(&self) -> Result<TestSuiteResponse> {
        let suites: Vec<TestSuiteInfo> = self.test_suites
            .values()
            .map(|suite| {
                let status = self.active_executions
                    .values()
                    .find(|exec| exec.suite_id == suite.id)
                    .map(|exec| exec.status.clone())
                    .unwrap_or(ExecutionStatus::Pending);
                
                TestSuiteInfo {
                    id: suite.id,
                    name: suite.name.clone(),
                    path: suite.path.clone(),
                    test_case_count: suite.test_cases.len(),
                    status,
                }
            })
            .collect();
        
        Ok(TestSuiteResponse::SuiteList { suites })
    }
}

#[async_trait]
impl Actor for TestSuiteActor {
    async fn started(&mut self, _ctx: &mut ActorContext) {
        tracing::info!("TestSuiteActor started");
    }
}

#[async_trait]
impl Handler<TestSuiteMessage> for TestSuiteActor {
    async fn handle(&mut self, msg: TestSuiteMessage, ctx: &mut ActorContext) -> TestSuiteResponse {
        match msg {
            TestSuiteMessage::LoadTestSuite { path } => {
                match self.load_test_suite(path).await {
                    Ok(response) => response,
                    Err(e) => TestSuiteResponse::Error {
                        message: e.to_string(),
                    },
                }
            }

            TestSuiteMessage::ExecuteSuite { suite_id, config } => {
                match self.execute_suite(ctx, suite_id, config).await {
                    Ok(response) => response,
                    Err(e) => TestSuiteResponse::Error {
                        message: e.to_string(),
                    },
                }
            }

            TestSuiteMessage::GetSuiteStatus { suite_id } => {
                match self.get_suite_status(suite_id).await {
                    Ok(response) => response,
                    Err(e) => TestSuiteResponse::Error {
                        message: e.to_string(),
                    },
                }
            }

            TestSuiteMessage::StopExecution { suite_id } => {
                match self.stop_execution(suite_id).await {
                    Ok(response) => response,
                    Err(e) => TestSuiteResponse::Error {
                        message: e.to_string(),
                    },
                }
            }

            TestSuiteMessage::ListSuites => {
                match self.list_suites().await {
                    Ok(response) => response,
                    Err(e) => TestSuiteResponse::Error {
                        message: e.to_string(),
                    },
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::actors::{PluginActor, AIOrchestrationActor, ReportActor};
    use coerce::actor::system::ActorSystem;
    use coerce::actor::scheduler::ActorType;

    #[tokio::test]
    async fn test_test_suite_actor_creation() {
        let system = ActorSystem::new();
        
        let plugin_actor = system.new_actor("plugin".to_string(), PluginActor::new(), ActorType::Tracked).await.unwrap();
        let ai_actor = system.new_actor("ai".to_string(), AIOrchestrationActor::new(), ActorType::Tracked).await.unwrap();
        let report_actor = system.new_actor("report".to_string(), ReportActor::new(), ActorType::Tracked).await.unwrap();

        let test_suite_actor = TestSuiteActor::new(plugin_actor.into(), ai_actor.into(), report_actor.into());
        
        assert_eq!(test_suite_actor.test_suites.len(), 0);
        assert_eq!(test_suite_actor.active_executions.len(), 0);
        
        system.shutdown().await;
    }
}
